package server

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"net/http"
	"net/http/httputil"
	"raft-kv/raft"
)

type ApiService struct {
	port                 int
	raft                 *raft.RaftServer
	router               *gin.Engine
	leaderServiceAddress string
	leaderProxy          *httputil.ReverseProxy
}

func NewApiService(port int, raft *raft.RaftServer) *ApiService {
	router := gin.Default()
	return &ApiService{
		port:   port,
		raft:   raft,
		router: router,
	}
}

func (this *ApiService) Start() error {
	this.router.Use(this.proxyHandler())

	this.router.POST("/get", this.Get)
	this.router.POST("/put", this.Put)
	this.router.POST("/delete", this.Delete)
	this.router.POST("/inc", this.Inc)

	address := fmt.Sprintf(":%d", this.port)
	return this.router.Run(address)
}

type Request struct {
	Key   int64 `json:"key"`
	Value int64 `json:"value"`
}

type Response struct {
	Error string `json:"error"`
	Value int64  `json:"value"`
}

func (this *ApiService) Get(ctx *gin.Context) {
	req := &Request{}
	if err := ctx.ShouldBindJSON(req); err != nil {
		ctx.JSON(http.StatusBadRequest, Response{
			Error: err.Error(),
		})
		return
	}
	result, err := this.raft.ApplyCommand(raft.CommandGet, req.Key, req.Value)
	if err != nil {
		ctx.JSON(http.StatusInternalServerError, Response{
			Error: err.Error(),
		})
		return
	}
	ctx.JSON(http.StatusOK, Response{
		Value: result.Value,
	})
}

func (this *ApiService) Put(ctx *gin.Context) {
	req := &Request{}
	if err := ctx.ShouldBindJSON(req); err != nil {
		ctx.JSON(http.StatusBadRequest, Response{
			Error: err.Error(),
		})
		return
	}
	result, err := this.raft.ApplyCommand(raft.CommandPut, req.Key, req.Value)
	if err != nil {
		ctx.JSON(http.StatusInternalServerError, Response{
			Error: err.Error(),
		})
		return
	}
	ctx.JSON(http.StatusOK, Response{
		Value: result.Value,
	})
}

func (this *ApiService) Delete(ctx *gin.Context) {
	req := &Request{}
	if err := ctx.ShouldBindJSON(req); err != nil {
		ctx.JSON(http.StatusBadRequest, Response{
			Error: err.Error(),
		})
		return
	}
	result, err := this.raft.ApplyCommand(raft.CommandDelete, req.Key, req.Value)
	if err != nil {
		ctx.JSON(http.StatusInternalServerError, Response{
			Error: err.Error(),
		})
		return
	}
	ctx.JSON(http.StatusOK, Response{
		Value: result.Value,
	})
}

func (this *ApiService) Inc(ctx *gin.Context) {
	req := &Request{}
	if err := ctx.ShouldBindJSON(req); err != nil {
		ctx.JSON(http.StatusBadRequest, Response{
			Error: err.Error(),
		})
		return
	}
	result, err := this.raft.ApplyCommand(raft.CommandInc, req.Key, req.Value)
	if err != nil {
		ctx.JSON(http.StatusInternalServerError, Response{
			Error: err.Error(),
		})
		return
	}
	ctx.JSON(http.StatusOK, Response{
		Value: result.Value,
	})
}

func (this *ApiService) proxyHandler() gin.HandlerFunc {
	return func(context *gin.Context) {
		if this.raft.IsLeader() {
			context.Next()
		} else {
			leaderServiceAddress := this.raft.GetLeaderServiceAddress()
			if this.leaderServiceAddress != leaderServiceAddress {
				Director := func(req *http.Request) {
					req.URL.Scheme = "http"
					req.URL.Host = leaderServiceAddress
				}
				this.leaderProxy = &httputil.ReverseProxy{
					Director: Director,
				}
				this.leaderServiceAddress = leaderServiceAddress
			}
			this.leaderProxy.ServeHTTP(context.Writer, context.Request)
			context.Abort()
		}
	}
}
